package net.matuschek.util;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.Socket;

/**
 * This class offers a timeout feature on socket connections.
 * A maximum length of time allowed for a connection can be 
 * specified, along with a host and port.
 *
 * @author David Reilly (written for JavaWorld)
 * @author Daniel Matuschek
 * @version $Id: TimedSocket.java,v 1.2 2002/05/31 14:45:56 matuschd Exp $
 *
 * imported to the net.matuschek.util source tree by Daniel Matuschek 
 */
public class TimedSocket
{

  /**
   * Attempts to connect to a service at the specified address
   * and port, for a specified maximum amount of time.
   *
   * @param addr  Address of host
   * @param port  Port of service
   * @param delay Delay in milliseconds
   */
  public static Socket getSocket ( InetAddress addr, int port, int delay) 
    throws InterruptedIOException, IOException
  {
    // Create a new socket thread, and start it running
    SocketThread st = new SocketThread( addr, port );
    st.start();
    
    int timer = 0;
    Socket sock = null;
    
    for (;;) {
      // Check to see if a connection is established
      
      if (st.isConnected()) {
	// Yes ...  assign to sock variable, and break out of loop
	sock = st.getSocket();
	break;
      } else {
	// Check to see if an error occurred
	if (st.isError()) {
	  // No connection could be established
	  throw (st.getException());
	}
	
	try {
	  // Sleep for a short period of time
	  Thread.sleep ( POLL_DELAY );
	} catch (InterruptedException ie) {}
	
	// Increment timer
	timer += POLL_DELAY;
	
	// Check to see if time limit exceeded
	if (timer > delay) {
	  // Can't connect to server
	  throw new InterruptedIOException("Could not connect for " + 
					   delay + " milliseconds");
	}
      }
    }
    
    return sock;
  }
  
  /**
   * Attempts to connect to a service at the specified address
   * and port, for a specified maximum amount of time.
   *
   * @param host  Hostname of machine
   * @param port  Port of service
   * @param delay Delay in milliseconds
   */
  public static Socket getSocket ( String host, int port, int delay) 
    throws InterruptedIOException, IOException
  {
    // Convert host into an InetAddress, and call getSocket method
    InetAddress inetAddr = InetAddress.getByName (host);
    
    return getSocket ( inetAddr, port, delay );
  }
  
  
  /**
   * Inner class for establishing a socket thread
   * within another thread, to prevent blocking.
   */
  static class SocketThread extends Thread
  {
    // Socket connection to remote host
    volatile private Socket m_connection = null;
    // Hostname to connect to
    private String m_host       = null;
    // Internet Address to connect to
    private InetAddress m_inet  = null;
    // Port number to connect to
    private int    m_port       = 0;
    // Exception in the event a connection error occurs
    private IOException m_exception = null;
    
    // Connect to the specified host and port number
    public SocketThread ( String host, int port) {
      // Assign to member variables
      m_host = host;
      m_port = port;
    }
    
    // Connect to the specified host IP and port number
    public SocketThread ( InetAddress inetAddr, int port ) {
      // Assign to member variables
      m_inet = inetAddr;
      m_port = port;
    }
    
    public void run() {
      // Socket used for establishing a connection
      Socket sock = null;
      
      try {
	// Was a string or an inet specified
	if (m_host != null) {
	  // Connect to a remote host - BLOCKING I/O
	  sock = new Socket (m_host, m_port);
	} else {
	  // Connect to a remote host - BLOCKING I/O
	  sock = new Socket (m_inet, m_port);
	}
      }
      catch (IOException ioe) {
	// Assign to our exception member variable
	m_exception = ioe;
	return;
      }

      // If socket constructor returned without error,
      // then connection finished
      m_connection = sock;
    }

    // Are we connected?
    public boolean isConnected() {
      if (m_connection == null)
	return false;
      else
	return true;
    }

    // Did an error occur?
    public boolean isError() {
      if (m_exception == null)
	return false;
      else
	return true;
    }

    // Get socket
    public Socket getSocket() {
      return m_connection;
    }
    
    // Get exception
    public IOException getException() {
      return m_exception;
    }
  }
  
  /** Polling delay for socket checks (in milliseconds) */
  private static final int POLL_DELAY = 100;
}
